﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Runtime.Remoting.Messaging;
using System.ComponentModel;
using UserQueries = SlamCms.SharePoint.UserManagement.Data.Queries;
using SlamCms.SharePoint.Data;
using Slam.Configuration;
using Slam;
using System.Data.EntityClient;
using System.Threading;
using Slam.Logging;
using SlamCms.SharePoint.Core;

namespace SlamCms.SharePoint.UserManagement
{
	public class UserGroupSynchronizer
	{
		private string _siteCollectionUrl;

		private static object _lock;
		
		public UserGroupSynchronizer(string siteCollectionUrl)
		{
			_siteCollectionUrl = siteCollectionUrl;
			if (_lock == null)
			{
				_lock = new object();
			}
		}

		public void Run()
		{
			lock (_lock)
			{
				SPSecurity.RunWithElevatedPrivileges(delegate()
				{
					ILogger logger = ComponentService.GetInstance<ILogger>();
					logger.LogMessage("Information", "UserGroupSynchronizer - Starting Sync");
					SPSite siteCollection = new SPSite(_siteCollectionUrl);
					if (siteCollection != null)
					{
						//Determine schema and connection string using SLAM Configuration
						string dataSchema = "dbo";
						string connectionString = "";
						IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
						DataMapping currentDataMapping = slamConfiguration.DataMapping;
						string siteCollectionUrl = siteCollection.ServerRelativeUrl.TrimStart('/');
						if (!String.IsNullOrEmpty(siteCollectionUrl) && slamConfiguration.DataMappings.ContainsKey(siteCollectionUrl))
						{
							currentDataMapping = slamConfiguration.DataMappings[siteCollectionUrl];
						}
						if (slamConfiguration is XmlConfigurationManager)
						{
							dataSchema = ((XmlSqlDataMapping)currentDataMapping).GetDataSchema();
							connectionString = ((XmlSqlDataMapping)currentDataMapping).GetConnectionString();
						}

						if (String.IsNullOrEmpty(connectionString))
						{
							connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
						}
						else
						{
							connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(connectionString);
						}

						string modelName = "Data.SlamCms";
						EntityConnectionStringBuilder entityConnection = new EntityConnectionStringBuilder();
						entityConnection.Provider = "System.Data.SqlClient";
						if (!connectionString.Contains("Persist Security Info"))
						{
							connectionString = connectionString.TrimEnd(';') + ";Persist Security Info=True;";
						}
						entityConnection.ProviderConnectionString = connectionString;
						entityConnection.Metadata = String.Format("res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl", modelName);

						//Create object context and set schema
						Entities context = new Entities(entityConnection.ToString());
						context.SetStoreSchema(modelName, dataSchema);

						//Ensure tables exists
						if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifyTableExists, dataSchema, "Group")).FirstOrDefault() == 0)
						{
							context.ExecuteStoreCommand(String.Format(UserQueries.CreateGroupTable, dataSchema));
						}

						if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifyTableExists, dataSchema, "GroupSite")).FirstOrDefault() == 0)
						{
							context.ExecuteStoreCommand(String.Format(UserQueries.CreateGroupSiteTable, dataSchema));
						}

						if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifyTableExists, dataSchema, "UserGroup")).FirstOrDefault() == 0)
						{
							context.ExecuteStoreCommand(String.Format(UserQueries.CreateUserGroupTable, dataSchema));
						}


						//Add synch column and trigger to connector tables
						context.ExecuteStoreCommand(String.Format(UserQueries.AddIsSynchedColumn, dataSchema, "GroupSite"));
						if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifySynchUpdateTriggerExists, dataSchema, "GroupSite")).FirstOrDefault() == 0)
						{
							context.ExecuteStoreCommand(String.Format(UserQueries.AddSynchUpdateTrigger, dataSchema, "GroupSite", "GroupID", "SiteID"));
						}

						context.ExecuteStoreCommand(String.Format(UserQueries.AddIsSynchedColumn, dataSchema, "UserGroup"));
						if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifySynchUpdateTriggerExists, dataSchema, "UserGroup")).FirstOrDefault() == 0)
						{
							context.ExecuteStoreCommand(String.Format(UserQueries.AddSynchUpdateTrigger, dataSchema, "UserGroup", "UserID", "GroupID"));
						}

						try
						{
							SiteCollection siteCollectionItem = (from item in context.SiteCollections
																 where item.SiteCollectionID == siteCollection.ID
																 select item).FirstOrDefault();

							if (siteCollectionItem == null)
							{
								SiteCollection newSiteCollectionItem = new SiteCollection();
								newSiteCollectionItem.SiteCollectionID = siteCollection.ID;
								newSiteCollectionItem.ServerRelativeUrl = siteCollection.ServerRelativeUrl;
								context.AddToSiteCollections(newSiteCollectionItem);
								context.SaveChanges(true);

								siteCollectionItem = newSiteCollectionItem;
							}

							Dictionary<int, int> syncedSiteCollectionGroupIds = new Dictionary<int, int>();

							string fullPermissionMask = Convert.ToString((long)SPBasePermissions.FullMask);
							

							var siteCollectionSites = (from s in context.Sites.Include("Groups")
													   where s.SiteCollection.SiteCollectionID == siteCollection.ID
													   select s).ToList();

							foreach (var siteCollectionSite in siteCollectionSites)
							{
								foreach (var group in siteCollectionSite.Groups)
								{
									if (!syncedSiteCollectionGroupIds.ContainsKey(group.SiteCollectionGroupID))
										syncedSiteCollectionGroupIds.Add(group.SiteCollectionGroupID, group.GroupID);
								}
							}

							//Add Site Collection Admin group
							Group adminGroup = null;
							if (syncedSiteCollectionGroupIds.ContainsKey(0))
							{
								int groupId = syncedSiteCollectionGroupIds[0];
								adminGroup = context.Groups.Where(g => g.GroupID == groupId).FirstOrDefault();
							}

							if (adminGroup == null)
							{
								adminGroup = new Group();
								adminGroup.SiteCollectionGroupID = 0;
								adminGroup.Name = fullPermissionMask;
								adminGroup.Add = true;
								adminGroup.Approve = true;
								adminGroup.Delete = true;
								adminGroup.Edit = true;
								adminGroup.View = true;
								context.AddToGroups(adminGroup);
								context.SaveChanges(true);
							}

							if (!syncedSiteCollectionGroupIds.ContainsKey(adminGroup.SiteCollectionGroupID))
								syncedSiteCollectionGroupIds.Add(adminGroup.SiteCollectionGroupID, adminGroup.GroupID);

							using (SPWeb rootWeb = siteCollection.RootWeb)
							{
								foreach (SPUser user in rootWeb.SiteAdministrators)
								{
									string userName = user.LoginName.Contains("|") ? user.LoginName.Split('|').Last() : user.LoginName;
									User userItem = context.Users.Where(u => u.UserName == userName).FirstOrDefault();
									if (userItem == null)
									{
										userItem = new User();
										userItem.UserName = userName;
										userItem.Name = user.Name;
										userItem.Groups.Add(adminGroup);
										userItem.Email = user.Email;
										userItem.Language = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
										context.AddToUsers(userItem);
										logger.LogMessage("Information", "UserGroupSynchronizer - Adding User: " + userName);
										context.SaveChanges(true);
									}
									else
									{
										if(String.IsNullOrEmpty(userItem.Name))
											userItem.Name = user.Name;
										if (String.IsNullOrEmpty(userItem.Email))
											userItem.Email = user.Email;
										if (String.IsNullOrEmpty(userItem.Language))
											userItem.Language = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
										
										userItem.Groups.Load();
										if (userItem.Groups.Where(g => g.GroupID == adminGroup.GroupID).FirstOrDefault() == null)
										{
											userItem.Groups.Add(adminGroup);
											logger.LogMessage("Information", "UserGroupSynchronizer - Adding Group: " + adminGroup.Name);
											context.SaveChanges(true);
										}
										else
										{
											context.ExecuteStoreCommand(String.Format(UserQueries.UpdateIsSynched, dataSchema, "UserGroup", "UserID", "GroupID", userItem.UserID.ToString(), adminGroup.GroupID.ToString()));
										}
									}
								}
							}

							foreach (SPWeb web in siteCollection.AllWebs)
							{
								Site siteItem = context.Sites.Where(s => s.SiteID == web.ID).FirstOrDefault();
								if (siteItem == null)
								{
									siteItem = new Site();
									siteItem.SiteID = web.ID;
									siteItem.Title = web.Title;
									siteItem.Name = web.Name;
									siteItem.Url = web.ServerRelativeUrl == "/" ? web.ServerRelativeUrl : web.ServerRelativeUrl.Substring(siteCollection.ServerRelativeUrl.Length);
									siteItem.SiteCollection = siteCollectionItem;
									context.AddToSites(siteItem);
									logger.LogMessage("Information", "UserGroupSynchronizer - Adding Site: " + siteItem.Name);
									context.SaveChanges(true);
								}

								if (adminGroup != null)
								{
									adminGroup.Sites.Load();
									if (adminGroup.Sites.Where(s => s.SiteID == web.ID).FirstOrDefault() == null)
									{
										adminGroup.Sites.Add(siteItem);
									}
									else
									{
										context.ExecuteStoreCommand(String.Format(UserQueries.UpdateIsSynched, dataSchema, "GroupSite", "GroupID", "SiteID", adminGroup.GroupID.ToString(), siteItem.SiteID.ToString()));
									}

									context.SaveChanges(true);
								}

								foreach (SPGroup group in web.Groups)
								{
									var roleAssignment = web.RoleAssignments.GetAssignmentByPrincipal(group);

									Group groupItem = null;

									if (syncedSiteCollectionGroupIds.ContainsKey(group.ID))
									{
										int groupId = syncedSiteCollectionGroupIds[group.ID];
										groupItem = context.Groups.Where(g => g.GroupID == groupId).FirstOrDefault();
									}
									else
									{
										siteItem.Groups.Load();
										groupItem = siteItem.Groups.Where(g => g.SiteCollectionGroupID == group.ID).FirstOrDefault();
									}

									if (groupItem == null)
									{
										groupItem = new Group();
										groupItem.SiteCollectionGroupID = group.ID;
										groupItem.Name = group.Name;
										foreach (var roleDefinitionBinding in roleAssignment.RoleDefinitionBindings.Cast<SPRoleDefinition>())
										{
											groupItem.Add = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.AddListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											groupItem.Approve = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.ApproveItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											groupItem.Delete = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.DeleteListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											groupItem.Edit = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.EditListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											groupItem.View = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.ViewListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
										}
										groupItem.Sites.Add(siteItem);
										context.AddToGroups(groupItem);
										logger.LogMessage("Information", "UserGroupSynchronizer - Adding Group: " + groupItem.Name);
										context.SaveChanges(true);
									}
									else
									{
										groupItem.Name = group.Name;
										foreach (var roleDefinitionBinding in roleAssignment.RoleDefinitionBindings.Cast<SPRoleDefinition>())
										{
											groupItem.Add = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.AddListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											groupItem.Approve = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.ApproveItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											groupItem.Delete = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.DeleteListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											groupItem.Edit = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.EditListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											groupItem.View = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.ViewListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
										}
										groupItem.Sites.Load();
										if (groupItem.Sites.Where(s => s.SiteID == web.ID).FirstOrDefault() == null)
										{
											groupItem.Sites.Add(siteItem);
										}
										else
										{
											context.ExecuteStoreCommand(String.Format(UserQueries.UpdateIsSynched, dataSchema, "GroupSite", "GroupID", "SiteID", groupItem.GroupID.ToString(), siteItem.SiteID.ToString()));
										}
										context.SaveChanges(true);
									}

									if (!syncedSiteCollectionGroupIds.ContainsKey(groupItem.SiteCollectionGroupID))
										syncedSiteCollectionGroupIds.Add(groupItem.SiteCollectionGroupID, groupItem.GroupID);

									foreach (SPUser user in group.Users)
									{
										string userName = user.LoginName.Contains("|") ? user.LoginName.Split('|').Last() : user.LoginName;
										User userItem = context.Users.Where(u => u.UserName == userName).FirstOrDefault();
										if (userItem == null)
										{
											userItem = new User();
											userItem.UserName = userName;
											userItem.Name = user.Name;
											userItem.Groups.Add(groupItem);
											userItem.Email = user.Email;
											userItem.Language = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
											context.AddToUsers(userItem);
											logger.LogMessage("Information", "UserGroupSynchronizer - Adding User: " + userName);
											context.SaveChanges(true);
										}
										else
										{
											userItem.Groups.Load();
											if (userItem.Groups.Where(g => g.GroupID == groupItem.GroupID).FirstOrDefault() == null)
											{
												userItem.Groups.Add(groupItem);
												context.SaveChanges(true);
											}
											else
											{
												context.ExecuteStoreCommand(String.Format(UserQueries.UpdateIsSynched, dataSchema, "UserGroup", "UserID", "GroupID", userItem.UserID.ToString(), groupItem.GroupID.ToString()));
											}
										}
									}
								}

								web.Dispose();
							}

						}
						catch (Exception ex)
						{
							
							logger.LogMessage("Exception", "User Group Synchronizer Exception: " + ex.ToString());
						}
						siteCollection.Dispose();

						//Remove IsSynched column and synch trigger
						context.ExecuteStoreCommand(String.Format(UserQueries.ClearNonSynchedSiteCollectionUserGroupData, dataSchema, "UserGroup", siteCollection.ID.ToString()));
						context.ExecuteStoreCommand(String.Format(UserQueries.ClearNonSynchedSiteCollectionGroupSiteData, dataSchema, "GroupSite", siteCollection.ID.ToString()));
						context.ExecuteStoreCommand(String.Format(UserQueries.RemoveSynchUpdateTrigger, dataSchema, "GroupSite"));
						context.ExecuteStoreCommand(String.Format(UserQueries.RemoveSynchUpdateTrigger, dataSchema, "UserGroup"));
						context.ExecuteStoreCommand(String.Format(UserQueries.RemoveIsSynchedColumn, dataSchema, "GroupSite"));
						context.ExecuteStoreCommand(String.Format(UserQueries.RemoveIsSynchedColumn, dataSchema, "UserGroup"));

						SlamCmsContext.Data.Cache.InvalidateCore();
						SlamCmsContext.Data.Cache.InvalidateTags();
						SlamCmsContext.Data.Cache.InvalidateUsers();
					}
					logger.LogMessage("Information", "UserGroupSynchronizer - Ending Sync");
				});
			}
		}

		public void RunAsync()
		{	
			Thread runThread = new Thread(new ThreadStart(Run));
			runThread.Start();
		}
	}
}
